﻿using System;
using System.Text;
using System.Web.UI;
using System.Web.UI.WebControls;
using System.Data;
using System.Reflection;
using System.Collections;
using System.Runtime.InteropServices;
using System.ComponentModel;
using System.Globalization;
using GoogleMaps.Configuration;

namespace GoogleMaps.StaticMaps
{
	/// <summary>
	/// Used to create an image that represents the map as it has been configured.
	/// </summary>
	[GuidAttribute("72293A47-BA70-4172-A0E8-C34DA7563DD2"),
	ToolboxData("<{0}:StaticMap runat=\"server\"></{0}:StaticMap>")]
	public class StaticMap : WebControl
	{
		private Byte zoomLevel;
		private Boolean isZoomLevelSet;
		private string apiKey;

		/// <summary>
		/// Initializes a new instance of the StaticMap class.
		/// </summary>
		public StaticMap()
		{
			Markers = new MarkerCollection();
		}

		/// <summary>
		/// Renders the control as JavaScript to the specified Html Writer (Overrides WebControl.Render(HtmlTextWriter).)
		/// </summary>
		/// <param name="writer">The HtmlTextWriter object that receives the control content.</param>
		[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Globalization", "CA1308:NormalizeStringsToUppercase")]
		protected override void Render(System.Web.UI.HtmlTextWriter writer)
		{
			if (String.IsNullOrEmpty(ApiKey))
				throw new InvalidOperationException(Properties.Resources.ApiKeyNullOrEmpty);

			if (MapCenter == null && (Markers == null || Markers.Count == 0))
				throw new InvalidOperationException(Properties.Resources.MapCenterOrMarkersRequired);

			if (!isZoomLevelSet && (Markers == null || Markers.Count == 0))
				throw new InvalidOperationException(Properties.Resources.ZoomLevelOrMarkersRequired);

			StringBuilder sb = new StringBuilder();

			// size - required
			sb.AppendFormat(CultureInfo.InvariantCulture, "size={0}x{1}", Width.Value.ToString(CultureInfo.InvariantCulture), Height.Value.ToString(CultureInfo.InvariantCulture));

			// center - optional
			if (MapCenter != null)
				sb.AppendFormat(CultureInfo.InvariantCulture, "&center={0},{1}", MapCenter.Lat.ToString(CultureInfo.InvariantCulture), MapCenter.Lng.ToString(CultureInfo.InvariantCulture));

			// zoom - optional
			if (isZoomLevelSet)
				sb.AppendFormat(CultureInfo.InvariantCulture, "&zoom={0}", ZoomLevel.ToString(CultureInfo.InvariantCulture));

			// map type - options
			switch (MapType)
			{
				case StaticMapType.Mobile:
					sb.Append("&maptype=mobile");
					break;
				case StaticMapType.Roadmap:
					sb.Append("&maptype=roadmap");
					break;
			}

			// markers - optional
			if (Markers != null && Markers.Count > 0)
			{
				sb.Append("&markers=");

				for (Int32 i = 0; i < Markers.Count; i++)
				{
					sb.AppendFormat(CultureInfo.InvariantCulture, 
						"{0}{1},{2},{3}{4}",
						(i > 0) ? "|" : "",
						Markers[i].Lat.ToString(CultureInfo.InvariantCulture),
						Markers[i].Lng.ToString(CultureInfo.InvariantCulture),
						(Markers[i].Color != MarkerColor.Default) ? Markers[i].Color.ToString().ToLower(CultureInfo.InvariantCulture) : "",
						Markers[i].AlphaCharacter);
				}
			}

			// key - required
			sb.AppendFormat(CultureInfo.InvariantCulture, "&key={0}", ApiKey);

			writer.AddAttribute(HtmlTextWriterAttribute.Src, String.Format(CultureInfo.InvariantCulture, "{0}{1}", Properties.Resources.StaticMapUri, sb.ToString()));
			writer.RenderBeginTag(HtmlTextWriterTag.Img);
			writer.RenderEndTag();
		}

		/// <summary>
		/// Gets or sets the center point for the map.
		/// </summary>
		[PersistenceMode(PersistenceMode.InnerProperty)]
		public GLatLng MapCenter { get; set; }

		/// <summary>
		/// Gets or sets the api key that is associated to the domain and required by Google
		/// before it will generate the map for the end user.
		/// </summary>
		public String ApiKey
		{
			get
			{
				// if the api key has been set directly then give it preference.
				if (!String.IsNullOrEmpty(apiKey))
					return apiKey;

				// we got here because a key hasn't been specified so get the config section
				GoogleMapsSection config = GoogleMapsSection.Section;

				if (config != null)
					return config.ApiKey;

				// we don't have an api key so just return null
				return null;
			}
			set { apiKey = value; }
		}

		/// <summary>
		/// Gets or sets the zoom level that should be used when rendering the map.
		/// </summary>
		public Byte ZoomLevel
		{
			get { return zoomLevel; }
			set
			{
				if (value > 19 || value < 0)
					throw new ArgumentOutOfRangeException(Properties.Resources.ZoomLevelOutOfRange);

				zoomLevel = value;
				isZoomLevelSet = true;
			}
		}

		/// <summary>
		/// Gets or sets the type of map that is being created.
		/// </summary>
		public StaticMapType MapType { get; set; }

		/// <summary>
		/// Gets a collection of markers that are associated with the map.
		/// </summary>
		[PersistenceMode(PersistenceMode.InnerProperty)]
		public MarkerCollection Markers { get; private set; }

		/// <summary>
		/// Gets or sets a datasource for the markers that should be placed on the map.
		/// </summary>
		public Object MarkersDataSource { get; set; }

		/// <summary>
		/// Gets or sets the name of the property in the databound object that holds the latitude value.
		/// </summary>
		/// <remarks>It is not necessary to provide this information if the object implements 
		/// GoogleMaps.StaticMaps.IGMarker</remarks>
		public String MarkerLatMember { get; set; }

		/// <summary>
		/// Gets or sets the name of the property in the databound object that holds the longitude value.
		/// </summary>
		/// <remarks>It is not necessary to provide this information if the object implements 
		/// GoogleMaps.StaticMaps.IGMarker</remarks>
		public String MarkerLngMember { get; set; }

		/// <summary>
		/// Gets or sets the name of the property in the databound object that holds the color value.
		/// </summary>
		/// <remarks>It is not necessary to provide this information if the object implements 
		/// GoogleMaps.StaticMaps.IGMarker</remarks>
		public String MarkerColorMember { get; set; }

		/// <summary>
		/// Gets or sets the name of the property in the databound object that holds the letter that
		/// will appear on the marker.
		/// </summary>
		/// <remarks>It is not necessary to provide this information if the object implements 
		/// GoogleMaps.StaticMaps.IGMarker</remarks>
		public String MarkerAlphaCharacterMember { get; set; }
		
		/// <summary>
		/// Binds a datasource to this server control so that the rendering of the control will produce
		/// all of the JavaScript necessary for the data to be represented on the map.
		/// </summary>
		public override void DataBind()
		{
			if (MarkersDataSource != null)
			{
				if (Markers == null)
					Markers = new MarkerCollection();

				if (MarkersDataSource is DataTable)
				{
					if (String.IsNullOrEmpty(MarkerLatMember)
						|| String.IsNullOrEmpty(MarkerLngMember))
						throw new InvalidOperationException(Properties.Resources.MarkerMembersNull);

					DataTable dt = (DataTable)MarkersDataSource;

					foreach (DataRow r in dt.Rows)
					{
						MarkerColor mc = MarkerColor.Default;

						if (!String.IsNullOrEmpty(MarkerColorMember))
						{
							if (r[MarkerColorMember] is String)
								mc = (MarkerColor)Enum.Parse(typeof(MarkerColor), r[MarkerColorMember].ToString());
							else if (r[MarkerColorMember] is Int32)
								mc = (MarkerColor)Convert.ToInt32(r[MarkerColorMember], CultureInfo.InvariantCulture);
						}

						if (!String.IsNullOrEmpty(MarkerAlphaCharacterMember))
						{
							Markers.Add(new Marker()
							{
								Lat = Convert.ToDecimal(r[MarkerLatMember], CultureInfo.InvariantCulture),
								Lng = Convert.ToDecimal(r[MarkerLngMember], CultureInfo.InvariantCulture),
								Color = mc,
								AlphaCharacter = r[MarkerAlphaCharacterMember].ToString()
							});
						}
						else
						{
							Markers.Add(new Marker()
							{
								Lat = Convert.ToDecimal(r[MarkerLatMember], CultureInfo.InvariantCulture),
								Lng = Convert.ToDecimal(r[MarkerLngMember], CultureInfo.InvariantCulture),
								Color = mc
							});
						}
					}
					return;
				}
				else if (MarkersDataSource is IEnumerable)
				{
					// first test to see if the guts of what we are looping over are IGMarker
					foreach (Object obj in (IEnumerable)MarkersDataSource)
					{
						IMarker im = obj as IMarker;
						if (im != null)
						{
							Markers.Add(new Marker()
							{
								Lat = im.Lat,
								Lng = im.Lng,
								Color = im.Color,
								AlphaCharacter = im.AlphaCharacter
							});
						}
						else
						{
							if (String.IsNullOrEmpty(MarkerLatMember)
								|| String.IsNullOrEmpty(MarkerLngMember))
								throw new InvalidOperationException(Properties.Resources.MarkerMembersNull);

							Type t = obj.GetType();
							PropertyInfo latInfo = t.GetProperty(MarkerLatMember);
							PropertyInfo lngInfo = t.GetProperty(MarkerLngMember);

							PropertyInfo colorInfo = null;
							if (!String.IsNullOrEmpty(MarkerColorMember))
								colorInfo = t.GetProperty(MarkerColorMember);
							
							PropertyInfo alphaCaracterInfo = null;
							if (!String.IsNullOrEmpty(MarkerAlphaCharacterMember))
								alphaCaracterInfo = t.GetProperty(MarkerAlphaCharacterMember);

							MarkerColor mc = MarkerColor.Default;

							if (colorInfo != null)
							{
								Int32? mcAsInt32 = colorInfo.GetValue(obj, null) as Int32?;
								if (mcAsInt32.HasValue)
									mc = (MarkerColor)mcAsInt32.Value;
								else
								{
									String mcAsString = colorInfo.GetValue(obj, null) as String;
									if (mcAsString != null)
										mc = (MarkerColor)Enum.Parse(typeof(MarkerColor), mcAsString);
								}
							}

							if (alphaCaracterInfo != null)
							{
								Markers.Add(new Marker()
								{
									Lat = Convert.ToDecimal(latInfo.GetValue(obj, null), CultureInfo.InvariantCulture),
									Lng = Convert.ToDecimal(lngInfo.GetValue(obj, null), CultureInfo.InvariantCulture),
									Color = mc,
									AlphaCharacter = alphaCaracterInfo.GetValue(obj, null).ToString()
								});
							}
							else
							{
								Markers.Add(new Marker()
								{
									Lat = Convert.ToDecimal(latInfo.GetValue(obj, null), CultureInfo.InvariantCulture),
									Lng = Convert.ToDecimal(lngInfo.GetValue(obj, null), CultureInfo.InvariantCulture),
									Color = mc
								});
							}
						}
					}

					return;
				}

				throw new InvalidOperationException(Properties.Resources.MarkersDataSourceNotValidType);
			}
		}
	}
}
